// <copyright file="ConfigurationHelper.cs" company="MaaAssistantArknights">
// Part of the MaaWpfGui project, maintained by the MaaAssistantArknights team (Maa Team)
// Copyright (C) 2021-2025 MaaAssistantArknights Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License v3.0 only as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
// </copyright>

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MaaWpfGui.Constants;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;

namespace MaaWpfGui.Helper;

public class ConfigurationHelper
{
    public static readonly string ConfigFile = Path.Combine(PathsHelper.ConfigDir, "gui.json");
    public static readonly string ConfigBakFile = Path.Combine(PathsHelper.ConfigDir, "gui.json.bak");
    public static readonly string ConfigErrorFile = Path.Combine(PathsHelper.ConfigDir, "gui.error.json");

    private static ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _kvsMap;
    private static string _current = ConfigurationKeys.DefaultConfiguration;
    private static ConcurrentDictionary<string, string> _kvs;
    private static ConcurrentDictionary<string, string> _globalKvs;

    private static readonly object _lock = new();

    private static readonly ILogger _logger = Log.ForContext<ConfigurationHelper>();

    public delegate void ConfigurationUpdateEventHandler(string key, string oldValue, string newValue);

    public static event ConfigurationUpdateEventHandler ConfigurationUpdateEvent;

    private static bool Released { get; set; }

    /// <summary>
    /// Get a configuration value
    /// </summary>
    /// <param name="key">The config key</param>
    /// <param name="defaultValue">The default value to return if the key is not existed</param>
    /// <returns>The config value</returns>
    public static string GetValue(string key, string defaultValue)
    {
        var hasValue = _kvs.TryGetValue(key, out var value);

        // _logger.Debug("Read configuration key {Key} with default value {DefaultValue}, configuration hit: {HasValue}, configuration value {Value}", key, defaultValue, hasValue, value);
        if (hasValue)
        {
            return value;
        }

        hasValue = _globalKvs.TryGetValue(key, out value);
        if (hasValue)
        {
            _logger.Information("Read configuration key {Key} with global configuration value {Value}, configuration hit: {HasValue}, configuration value {Value}", key, value, true, value);
            SetValue(key, value);
            return value;
        }

        // return hasValue ? value : defaultValue;
        SetValue(key, defaultValue);
        return defaultValue;
    }

    public static int GetValue(string key, int defaultValue)
    {
        var value = GetValue(key, defaultValue.ToString());
        return int.TryParse(value, out var result) ? result : defaultValue;
    }

    public static long GetValue(string key, long defaultValue)
    {
        var value = GetValue(key, defaultValue.ToString());
        return long.TryParse(value, out var result) ? result : defaultValue;
    }

    public static bool GetValue(string key, bool defaultValue)
    {
        var value = GetValue(key, defaultValue.ToString());
        return bool.TryParse(value, out var result) ? result : defaultValue;
    }

    public static TOut GetValue<TOut>(string key, TOut defaultValue)
        where TOut : struct, Enum
    {
        var value = GetValue(key, defaultValue.ToString());
        return Enum.TryParse<TOut>(value, out var result) ? result : defaultValue;
    }

    public static string GetGlobalValue(string key, string defaultValue)
    {
        var hasValue = _globalKvs.TryGetValue(key, out var value);

        // _logger.Debug("Read global configuration key {Key} with default value {DefaultValue}, configuration hit: {HasValue}, configuration value {Value}", key, defaultValue, hasValue, value);
        if (hasValue)
        {
            return value;
        }

        hasValue = _kvs.TryGetValue(key, out value);
        if (hasValue)
        {
            _logger.Information("Read global configuration key {Key} with current configuration value {Value}, configuration hit: {HasValue}, configuration value {Value}", key, value, true, value);
            SetGlobalValue(key, value);
            DeleteValue(key);
            return value;
        }

        // 保证有全局配置
        SetGlobalValue(key, defaultValue);
        return defaultValue;
    }

    public static int GetGlobalValue(string key, int defaultValue)
    {
        var value = GetGlobalValue(key, defaultValue.ToString());
        return int.TryParse(value, out var result) ? result : defaultValue;
    }

    public static long GetGlobalValue(string key, long defaultValue)
    {
        var value = GetGlobalValue(key, defaultValue.ToString());
        return long.TryParse(value, out var result) ? result : defaultValue;
    }

    public static bool GetGlobalValue(string key, bool defaultValue)
    {
        var value = GetGlobalValue(key, defaultValue.ToString());
        return bool.TryParse(value, out var result) ? result : defaultValue;
    }

    public static TOut GetGlobalValue<TOut>(string key, TOut defaultValue)
        where TOut : struct, Enum
    {
        var value = GetGlobalValue(key, defaultValue.ToString());
        return Enum.TryParse<TOut>(value, out var result) ? result : defaultValue;
    }

    /// <summary>
    /// Set a configuration value
    /// </summary>
    /// <param name="key">The config key</param>
    /// <param name="value">The config value</param>
    /// <returns>The return value of <see cref="Save"/></returns>
    public static bool SetValue(string key, string value)
    {
        var old = string.Empty;
        if (!_kvs.TryAdd(key, value))
        {
            old = _kvs[key];
            _kvs[key] = value;
        }

        var result = Save();
        if (result)
        {
            ConfigurationUpdateEvent?.Invoke(key, old, value);
            _logger.Debug("Configuration {Key} has been set to `{Value}`", key, value);
        }
        else
        {
            _logger.Warning("Failed to save configuration {Key} to `{Value}`", key, value);
        }

        return result;
    }

    public static bool SetGlobalValue(string key, string value)
    {
        var old = string.Empty;
        if (!_globalKvs.TryAdd(key, value))
        {
            old = _globalKvs[key];
            _globalKvs[key] = value;
        }

        var result = Save();
        if (result)
        {
            ConfigurationUpdateEvent?.Invoke(key, old, value);
            _logger.Debug("Global configuration {Key} has been set to `{Value}`", key, value);
        }
        else
        {
            _logger.Warning("Failed to save global configuration {Key} to `{Value}`", key, value);
        }

        return result;
    }

    public static bool ContainsKey(string key, bool isGlobal = false) => isGlobal ? _globalKvs.ContainsKey(key) : _kvs.ContainsKey(key);

    /// <summary>
    /// Deletes a configuration
    /// </summary>
    /// <param name="key">The configuration key.</param>
    /// <returns>The return value of <see cref="Save"/>.</returns>
    public static bool DeleteValue(string key) => DeleteValue(key, out _);

    /// <summary>
    /// Deletes a configuration
    /// </summary>
    /// <param name="key">The configuration key.</param>
    /// <param name="value">The old value.</param>
    /// <returns>The return value of <see cref="Save"/>.</returns>
    public static bool DeleteValue(string key, out string value)
    {
        value = string.Empty;
        if (_kvs.Remove(key, out var kv))
        {
            value = kv;
        }
        else
        {
            return false;
        }

        var result = Save();
        if (result)
        {
            ConfigurationUpdateEvent?.Invoke(key, value, string.Empty);
            _logger.Debug("Configuration {Key} has been deleted", key);
        }
        else
        {
            _logger.Warning("Failed to save configuration file when deleted {Key}", key);
        }

        return result;
    }

    public static bool DeleteGlobalValue(string key, out string value)
    {
        value = string.Empty;
        if (_globalKvs.Remove(key, out var kv))
        {
            value = kv;
        }
        else
        {
            return false;
        }

        var result = Save();
        if (result)
        {
            ConfigurationUpdateEvent?.Invoke(key, value, string.Empty);
            _logger.Debug("Global configuration {Key} has been deleted", key);
        }
        else
        {
            _logger.Warning("Failed to save global configuration file when deleted {Key}", key);
        }

        return result;
    }

    private static void EnsureConfigDirectoryExists()
    {
        if (Directory.Exists("config") is false)
        {
            Directory.CreateDirectory("config");
        }
    }

    private static bool ParseConfig(out JObject parsed)
    {
        parsed = ParseJsonFile(ConfigFile);
        if (parsed is not null)
        {
            return true;
        }

        if (File.Exists(ConfigFile))
        {
            _logger.Warning("Failed to load configuration file, copying configuration file to error file");
            File.Copy(ConfigFile, ConfigErrorFile, true);
        }

        if (File.Exists(ConfigBakFile))
        {
            _logger.Information("trying to use backup file");
            parsed = ParseJsonFile(ConfigBakFile);
            if (parsed is not null)
            {
                _logger.Information("Backup file loaded successfully, copying backup file to configuration file");
                File.Copy(ConfigBakFile, ConfigFile, true);
                return true;
            }
        }

        _logger.Warning("Failed to load configuration file and/or backup file, using default settings");

        _kvsMap = [];
        _current = ConfigurationKeys.DefaultConfiguration;
        _kvsMap[_current] = [];
        _kvs = _kvsMap[_current];
        _globalKvs = [];
        return false;
    }

    /// <summary>
    /// Load configuration file
    /// </summary>
    /// <returns>true if succeeded, false if failed</returns>
    public static bool Load()
    {
        EnsureConfigDirectoryExists();

        // Load configuration file
        if (!ParseConfig(out var parsed))
        {
            return false;
        }

        SetKvOrMigrate(parsed);
        if (Save(ConfigBakFile))
        {
            _logger.Information("{File} saved", ConfigBakFile);
        }
        else
        {
            _logger.Warning("{File} save failed", ConfigBakFile);
        }

        return true;
    }

    private static void SetKvOrMigrate(JObject parsed)
    {
        bool hasConfigMap = parsed.ContainsKey(ConfigurationKeys.ConfigurationMap);
        bool hasGlobalConfig = parsed.ContainsKey(ConfigurationKeys.GlobalConfiguration);
        if (hasConfigMap)
        {
            // new version
            _kvsMap = parsed[ConfigurationKeys.ConfigurationMap]?.ToObject<ConcurrentDictionary<string, ConcurrentDictionary<string, string>>>()
                      ?? [];
            _current = parsed[ConfigurationKeys.CurrentConfiguration]?.ToString()
                       ?? ConfigurationKeys.DefaultConfiguration;
        }
        else
        {
            // old version
            _logger.Information("Configuration file is in old version, migrating to new version");

            _kvsMap = [];
            _current = ConfigurationKeys.DefaultConfiguration;
            _kvsMap[_current] = parsed.ToObject<ConcurrentDictionary<string, string>>();
        }

        _kvs = _kvsMap[_current];

        _globalKvs = hasGlobalConfig
            ? parsed[ConfigurationKeys.GlobalConfiguration]?.ToObject<ConcurrentDictionary<string, string>>()
            : new ConcurrentDictionary<string, string>();
    }

    /// <summary>
    /// Save configuration file
    /// </summary>
    /// <returns>The result of saving process</returns>
    private static bool Save(string file = null)
    {
        lock (_lock)
        {
            if (Released)
            {
                _logger.Warning("Attempting to save configuration file after release, this is not allowed");
                return false;
            }

            try
            {
                var sortedKvsMap = new SortedDictionary<string, SortedDictionary<string, string>>(
                    _kvsMap.ToDictionary(
                        kvp => kvp.Key,
                        kvp => new SortedDictionary<string, string>(kvp.Value)));

                var jsonStr = JsonConvert.SerializeObject(
                    new SortedDictionary<string, object>
                    {
                        { ConfigurationKeys.ConfigurationMap, sortedKvsMap },
                        { ConfigurationKeys.CurrentConfiguration, _current },
                        { ConfigurationKeys.GlobalConfiguration, new SortedDictionary<string, string>(_globalKvs) },
                    },
                    Formatting.Indented);

                File.WriteAllText(file ?? ConfigFile, jsonStr);
            }
            catch (Exception e)
            {
                _logger.Error(e, "Failed to save configuration file.");
                return false;
            }

            return true;
        }
    }

    public static string GetCheckedStorage(string storageKey, string name, string defaultValue)
    {
        return GetValue(storageKey + name + ".IsChecked", defaultValue);
    }

    public static bool SetCheckedStorage(string storageKey, string name, string value)
    {
        return SetValue(storageKey + name + ".IsChecked", value);
    }

    public static string GetTimer(int i, string defaultValue)
    {
        return GetGlobalValue($"Timer.Timer{i + 1}", defaultValue);
    }

    public static bool SetTimer(int i, string value)
    {
        return SetGlobalValue($"Timer.Timer{i + 1}", value);
    }

    public static string GetTimerHour(int i, string defaultValue)
    {
        return GetGlobalValue($"Timer.Timer{i + 1}Hour", defaultValue);
    }

    public static bool SetTimerHour(int i, string value)
    {
        return SetGlobalValue($"Timer.Timer{i + 1}Hour", value);
    }

    public static string GetTimerMin(int i, string defaultValue)
    {
        return GetGlobalValue($"Timer.Timer{i + 1}Min", defaultValue);
    }

    public static bool SetTimerMin(int i, string value)
    {
        return SetGlobalValue($"Timer.Timer{i + 1}Min", value);
    }

    public static string GetTimerConfig(int i, string defaultValue)
    {
        return GetGlobalValue($"Timer.Timer{i + 1}.Config", defaultValue);
    }

    public static bool SetTimerConfig(int i, string value)
    {
        return SetGlobalValue($"Timer.Timer{i + 1}.Config", value);
    }

    public static string GetTaskOrder(string task, string defaultValue)
    {
        return GetValue("TaskQueue.Order." + task, defaultValue);
    }

    public static bool SetTaskOrder(string task, string value)
    {
        return SetValue("TaskQueue.Order." + task, value);
    }

    public static int GetSettingOrder(string setting, int defaultValue)
    {
        var value = GetGlobalValue("Settings.Order." + setting, defaultValue.ToString());
        return int.TryParse(value, out var result) ? result : defaultValue;
    }

    public static bool SetSettingOrder(string setting, int value)
    {
        return SetGlobalValue("Settings.Order." + setting, value.ToString());
    }

    public static void Release()
    {
        lock (_lock)
        {
            if (Released)
            {
                return;
            }

            if (Save())
            {
                _logger.Information("{File} saved", ConfigFile);
            }
            else
            {
                _logger.Warning("{File} save failed", ConfigFile);
            }
        }

        Released = true;
    }

    private static JObject ParseJsonFile(string filePath)
    {
        if (File.Exists(filePath) is false)
        {
            return null;
        }

        var str = File.ReadAllText(filePath);

        try
        {
            var obj = (JObject)JsonConvert.DeserializeObject(str);
            return obj ?? throw new Exception("Failed to parse json file");
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Failed to deserialize json file: {FilePath}", filePath);
        }

        return null;
    }

    public static bool SwitchConfiguration(string configName)
    {
        if (_kvsMap.ContainsKey(configName) is false)
        {
            _logger.Warning("Configuration {ConfigName} does not exist", configName);
            return false;
        }

        _current = configName;
        _kvs = _kvsMap[_current];
        return true;
    }

    public static bool AddConfiguration(string configName, string copyFrom = null)
    {
        if (string.IsNullOrEmpty(configName))
        {
            return false;
        }

        if (_kvsMap.ContainsKey(configName))
        {
            _logger.Warning("Configuration {ConfigName} already exists", configName);
            return false;
        }

        if (copyFrom is null)
        {
            _kvsMap[configName] = [];
        }
        else
        {
            if (_kvsMap.ContainsKey(copyFrom) is false)
            {
                _logger.Warning("Configuration {ConfigName} does not exist", copyFrom);
                return false;
            }

            _kvsMap[configName] = new ConcurrentDictionary<string, string>(_kvsMap[copyFrom]);
        }

        return true;
    }

    public static bool DeleteConfiguration(string configName)
    {
        if (_kvsMap.ContainsKey(configName) is false)
        {
            _logger.Warning("Configuration {ConfigName} does not exist", configName);
            return false;
        }

        if (_current == configName)
        {
            _logger.Warning("Configuration {ConfigName} is current configuration, cannot delete", configName);
            return false;
        }

        _kvsMap.Remove(configName, out _);
        return true;
    }

    public static List<string> GetConfigurationList() => [.. _kvsMap.Keys.OrderBy(key => key)];

    public static string GetCurrentConfiguration() => _current;
}
